/*
 *
 *  *    Copyright 2020-2021 Luter.me
 *  *
 *  *    Licensed under the Apache License, Version 2.0 (the "License");
 *  *    you may not use this file except in compliance with the License.
 *  *    You may obtain a copy of the License at
 *  *
 *  *      http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *    Unless required by applicable law or agreed to in writing, software
 *  *    distributed under the License is distributed on an "AS IS" BASIS,
 *  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *    See the License for the specific language governing permissions and
 *  *    limitations under the License.
 *
 */

package com.luter.heimdall.core.token;

import com.luter.heimdall.core.config.ConfigManager;
import com.luter.heimdall.core.details.UserDetails;
import com.luter.heimdall.core.exception.HeimdallException;

import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.Objects;

/**
 * Token 模型
 * <p>
 * 1、作为 Session 机制使用，token 信息保存在服务端，将 TokenId 作为凭据发给客户端。
 * <p>
 * 2、作为 Jwt Token 机制使用，token 按照 jwt 格式产生加密串，作为凭据发给客户端。
 *
 * @author luter
 */
public class SimpleToken implements Serializable {
    /**
     * The constant serialVersionUID.
     */
    private static final long serialVersionUID = -3552778562536120490L;
    /**
     * 唯一 ID,以此唯一区分一个 token
     */
    private String id;
    /**
     * The Iss.  (issuer)：签发人
     */
    private String iss;
    /**
     * The Iat.(Issued At)：签发时间
     */
    private long iat;
    /**
     * The Exp.(expiration time)：过期时间
     */
    private long exp;
    /**
     * 客户端登录时候的 IP 地址
     * <p>
     * 注意：
     * 存在代理服务的情况下，获取到的未必是真实的客户端地址
     * <p>
     * 比如在有微服务网关、nginx 代理服务的情况下，代理网关需要配置
     * <p>
     * 转发携带真实 ip，才能正确获取客户端地址。
     *
     * <p>
     * md5 加密后存储
     */
    private String ria;
    /**
     * 用户信息详情
     */
    private UserDetails details;

    /**
     * Instantiates a new Base token.
     */
    public SimpleToken() {
        this.iss = ConfigManager.getConfig().getToken().getIss();
        this.ria = "";
    }

    /**
     * 给定 ID 产生 Token
     *
     * @param id          唯一 ID
     * @param duration    时长，单位:秒
     * @param userDetails the user details
     * @return token base token
     */
    public static SimpleToken build(String id, long duration, UserDetails userDetails) {
        if (null == userDetails) {
            throw new HeimdallException("The UserDetails can not be null");
        }
        SimpleToken token = new SimpleToken();
        return token.setId(id).setDetails(userDetails).initDuration(token, duration);
    }

    /**
     * 初始化 token 时间区间
     * <p>
     * 起始时间：当前时间
     * <p>
     * 结束时间：起始时间后延 duration秒
     *
     * @param token    the token
     * @param duration the duration
     * @return the simple token
     */
    public SimpleToken initDuration(SimpleToken token, long duration) {
        Date now = new Date();
        LocalDateTime dateTime = LocalDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault());
        LocalDateTime newDateTime = dateTime.plusSeconds(duration);
        Date exp = Date.from(newDateTime.atZone(ZoneId.systemDefault()).toInstant());
        //初始化签发时间和过期时间
        token.setIat(now.getTime()).setExp(exp.getTime());
        return token;
    }

    /**
     * 设置 token 过期时间，可实现续签
     *
     * @param token    the token
     * @param duration 有效时长
     * @return the simple token
     */
    public SimpleToken renewalToken(SimpleToken token, long duration) {
        Date now = new Date();
        LocalDateTime dateTime = LocalDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault());
        //从当前时间往后延期 duration 秒
        LocalDateTime newDateTime = dateTime.plusSeconds(duration);
        Date exp = Date.from(newDateTime.atZone(ZoneId.systemDefault()).toInstant());
        //设置过期时间
        token.setExp(exp.getTime());
        return token;
    }

    /**
     * 是否已经过期
     * <p>
     * exp 小于当前时间，则认为过期
     *
     * @return the boolean，true:已经过期，false:暂未过期
     */
    public boolean hasExpired() {
        return this.exp < System.currentTimeMillis();
    }

    /**
     * Gets iss.
     *
     * @return the iss
     */
    public String getIss() {
        return iss;
    }

    /**
     * Sets iss.
     *
     * @param iss the iss
     * @return the iss
     */
    public SimpleToken setIss(String iss) {
        this.iss = iss;
        return this;
    }

    /**
     * Gets id.
     *
     * @return the id
     */
    public String getId() {
        return id;
    }

    /**
     * Sets id.
     *
     * @param id the id
     * @return the id
     */
    public SimpleToken setId(String id) {
        this.id = id;
        return this;
    }

    /**
     * Gets iat.
     *
     * @return the iat
     */
    public long getIat() {
        return iat;
    }

    /**
     * Sets iat.
     *
     * @param iat the iat
     * @return the iat
     */
    public SimpleToken setIat(long iat) {
        this.iat = iat;
        return this;
    }

    /**
     * Gets exp.
     *
     * @return the exp
     */
    public long getExp() {
        return exp;
    }

    /**
     * Sets exp.
     *
     * @param exp the exp
     * @return the exp
     */
    public SimpleToken setExp(long exp) {
        this.exp = exp;
        return this;
    }

    /**
     * Gets user details.
     *
     * @return the user details
     */
    public UserDetails getDetails() {
        return details;
    }

    /**
     * Sets user details.
     *
     * @param details the user details
     * @return the user details
     */
    public SimpleToken setDetails(UserDetails details) {
        this.details = details;
        return this;
    }

    public String getRia() {
        return ria;
    }

    public SimpleToken setRia(String ria) {
        this.ria = ria;
        return this;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        SimpleToken token = (SimpleToken) o;
        return id.equals(token.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    @Override
    public String toString() {
        return "SimpleToken{" +
                "id='" + id + '\'' +
                ", iss='" + iss + '\'' +
                ", iat=" + iat +
                ", exp=" + exp +
                ", ria='" + ria + '\'' +
                ", details=" + details +
                '}';
    }
}
